/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.impl;

import java.util.ArrayList;
import java.util.List;
import xowl.interpreter.XOWLException;

/**
 *
 * @author Laurent WOUTERS
 */
public class AggregatedEvaluator implements Evaluator {
    private List<xowl.interpreter.impl.Evaluator> parts;
    private xowl.interpreter.Repository repository;

    public AggregatedEvaluator(xowl.interpreter.Repository repository) {
        this.parts = new ArrayList<xowl.interpreter.impl.Evaluator>();
        this.repository = repository;
    }

    public void addPart(xowl.interpreter.impl.Evaluator evaluator) { parts.add(evaluator); }
    public void removePart(xowl.interpreter.impl.Evaluator evaluator) { parts.remove(evaluator); }
    
    private xowl.interpreter.impl.Evaluator getPartFor(xowl.lang.owl2.Expression exp) throws xowl.interpreter.XOWLException {
        for (xowl.interpreter.impl.Evaluator evaluator : parts)
            if (evaluator.can(exp))
                return evaluator;
        throw new xowl.interpreter.XOWLException("No evaluator for " + exp.getClass().getName());
    }
    private xowl.interpreter.impl.Evaluator getPartFor(xowl.lang.actions.Query exp) throws xowl.interpreter.XOWLException {
        for (xowl.interpreter.impl.Evaluator evaluator : parts)
            if (evaluator.can(exp))
                return evaluator;
        throw new xowl.interpreter.XOWLException("No evaluator for " + exp.getClass().getName());
    }

    @Override public boolean can(xowl.lang.owl2.Expression exp) {
        for (xowl.interpreter.impl.Evaluator evaluator : parts)
            if (evaluator.can(exp))
                return true;
        return false;
    }
    @Override public boolean can(xowl.lang.actions.Query exp) {
        for (xowl.interpreter.impl.Evaluator evaluator : parts)
            if (evaluator.can(exp))
                return true;
        return false;
    }

    @Override public xowl.lang.runtime.Value evaluate(Evaluator top, xowl.lang.owl2.Expression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evaluate(top, expression);
    }

    @Override public xowl.lang.runtime.Entity evalEntity(Evaluator top, xowl.lang.owl2.EntityExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalEntity(top, expression);
    }
    @Override public xowl.lang.runtime.Class evalClass(Evaluator top, xowl.lang.owl2.ClassExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalClass(top, expression);
    }
    @Override public xowl.lang.runtime.ObjectProperty evalObjProp(Evaluator top, xowl.lang.owl2.ObjectPropertyExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalObjProp(top, expression);
    }
    @Override public xowl.lang.runtime.DataProperty evalDataProp(Evaluator top, xowl.lang.owl2.DataPropertyExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalDataProp(top, expression);
    }
    @Override public xowl.lang.runtime.Datatype evalDatatype(Evaluator top, xowl.lang.owl2.Datarange expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalDatatype(top, expression);
    }
    @Override public xowl.lang.runtime.Individual evalIndividual(Evaluator top, xowl.lang.owl2.IndividualExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalIndividual(top, expression);
    }
    @Override public xowl.lang.runtime.Literal evalLiteral(Evaluator top, xowl.lang.owl2.LiteralExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalLiteral(top, expression);
    }
    @Override public xowl.lang.runtime.Function evalFunction(Evaluator top, xowl.lang.actions.FunctionExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalFunction(top, expression);
    }
    @Override public xowl.lang.runtime.Closure evalClosure(Evaluator top, xowl.lang.actions.ExecutableExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalClosure(top, expression);
    }
    @Override public xowl.lang.runtime.Array evalArray(Evaluator top, xowl.lang.actions.ArrayExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalArray(top, expression);
    }
    @Override public xowl.lang.runtime.JavaObject evalJavaObject(Evaluator top, xowl.lang.interop.JavaObjectExpression expression) throws xowl.interpreter.XOWLException {
        if (expression == null)
            return null;
        return getPartFor(expression).evalJavaObject(top, expression);
    }

    @Override public List<xowl.interpreter.QuerySolution> select(Evaluator top, xowl.lang.actions.Select query) throws xowl.interpreter.XOWLException {
        if (query == null)
            return null;
        return getPartFor(query).select(top, query);
    }


    @Override public xowl.lang.runtime.Entity toEntity(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return (xowl.lang.runtime.Entity)value;
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Entity");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Entity");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Entity");
            return toEntity(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            return toFunction(value).getInterpretationOf();
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toEntity((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Entity");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Class toClass(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsClass((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Class");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Class");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Class");
            return toClass(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to Class");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toClass((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Class");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.ObjectProperty toObjProp(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsObjectProperty((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to ObjectProperty");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to ObjectProperty");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to ObjectProperty");
            return toObjProp(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to ObjectProperty");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toObjProp((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to ObjectProperty");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.DataProperty toDataProp(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsDataProperty((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to DataProperty");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to DataProperty");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to DataProperty");
            return toDataProp(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to DataProperty");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toDataProp((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to DataProperty");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Datatype toDatatype(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsDatatype((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Datatype");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Datatype");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Datatype");
            return toDatatype(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to Datatype");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toDatatype((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Datatype");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Individual toIndividual(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsIndividual((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Individual");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            return ((xowl.lang.owl2.AnonymousIndividual)value);
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Individual");
            return toIndividual(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to Individual");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toIndividual((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Individual");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Literal toLiteral(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            throw new XOWLException("Cannot cast runtime value Entity to Literal");
        } else if (value instanceof xowl.lang.runtime.Literal) {
            return ((xowl.lang.runtime.Literal)value);
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Literal");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Literal");
            return toLiteral(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            throw new XOWLException("Cannot cast runtime value Closure to Literal");
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            JavaDatatype datatype = JavaDatatype.get(object.getType());
            xowl.lang.owl2.IRI iri = new xowl.lang.owl2.IRI();
            iri.setHasValue(datatype.getOWLDatatype());
            xowl.lang.runtime.Literal literal = new xowl.lang.runtime.Literal();
            literal.setMemberOf(evalDatatype(this, iri));
            literal.setLexicalValue(datatype.getToString(object.getObject()));
            return literal;
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Function toFunction(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            return repository.interpretAsFunction((xowl.lang.runtime.Entity)value);
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Function");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Function");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Function");
            return toFunction(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            xowl.lang.actions.QueryVariable var = new xowl.lang.actions.QueryVariable();
            xowl.lang.actions.ValueOf query = new xowl.lang.actions.ValueOf();
            xowl.lang.actions.FunctionDefinition axiom = new xowl.lang.actions.FunctionDefinition();
            var.setName("f");
            axiom.setBehavior(((xowl.lang.runtime.Closure)value).getCloses());
            axiom.setFunction(var);
            query.setOn(var);
            query.addAxioms(axiom);
            return evalFunction(this, query);
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toFunction((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Function");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Closure toClosure(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Entity) {
            throw new XOWLException("Cannot cast runtime value Entity to Closure");
        } else if (value instanceof xowl.lang.runtime.Literal) {
            throw new XOWLException("Cannot cast runtime value Literal to Closure");
        } else if (value instanceof xowl.lang.owl2.AnonymousIndividual) {
            throw new XOWLException("Cannot cast runtime value Anonymous Indiviudal to Closure");
        } else if (value instanceof xowl.lang.runtime.Array) {
            xowl.lang.runtime.Array array = (xowl.lang.runtime.Array)value;
            if (array.getAllElements().size() != 1)
                throw new XOWLException("Cannot cast runtime value Array[" + array.getAllElements().size() + "] to Closure");
            return toClosure(array.getAllElements().iterator().next().getValue());
        } else if (value instanceof xowl.lang.runtime.Closure) {
            return ((xowl.lang.runtime.Closure)value);
        } else if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toClosure((xowl.lang.runtime.Value)object.getObject());
            throw new XOWLException("Cannot cast runtime value JavaObject to Closure");
        }
        throw new XOWLException("Unknown runtime value type: " + value.getClass().getName());
    }
    @Override public xowl.lang.runtime.Array toArray(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.Array)
            return ((xowl.lang.runtime.Array)value);
        if (value instanceof xowl.lang.runtime.JavaObject) {
            // Check if this is a boxed xOWL value
            xowl.lang.runtime.JavaObject object = (xowl.lang.runtime.JavaObject)value;
            if (object.getType() == xowl.lang.runtime.Value.class)
                return toArray((xowl.lang.runtime.Value)object.getObject());
        }
        xowl.lang.runtime.Array array = new xowl.lang.runtime.Array();
        xowl.lang.runtime.Element elem = new xowl.lang.runtime.Element();
        elem.setIndex(0);
        elem.setValue(value);
        array.addElements(elem);
        return array;
    }
    @Override public xowl.lang.runtime.JavaObject toJavaObject(xowl.lang.runtime.Value value) throws xowl.interpreter.XOWLException {
        if (value == null) return null;
        if (value instanceof xowl.lang.runtime.JavaObject)
            return (xowl.lang.runtime.JavaObject)value;
        xowl.lang.runtime.JavaObject object = new xowl.lang.runtime.JavaObject();
        if (value instanceof xowl.lang.runtime.Literal) {
            // Convert to primitive and box
            xowl.lang.runtime.Literal lit = (xowl.lang.runtime.Literal)value;
            String datatype = lit.getMemberOf().getInterpretationOf().getHasIRI().getHasValue();
            JavaDatatype type = JavaDatatype.get(datatype);
            object.setObject(type.getToValue(lit.getLexicalValue()));
            object.setType(type.getJavaType());
        } else {
            // box the xOWL value
            object.setObject(value);
            object.setType(xowl.lang.runtime.Value.class);
        }
        return object;
    }
}
